/*
moxl - Modular XML Library
Copyright (c) 2007, Malcolm Sparks

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package moxl.xpath.parser;

import java.util.HashMap;
import java.util.Map;

import moxl.xpath.XPathContext;
import moxl.xpath.parser.operators.AndOperator;
import moxl.xpath.parser.operators.DivideOperator;
import moxl.xpath.parser.operators.EqualsOperator;
import moxl.xpath.parser.operators.GreaterThanOperator;
import moxl.xpath.parser.operators.GreaterThanOrEqualToOperator;
import moxl.xpath.parser.operators.LessThanOperator;
import moxl.xpath.parser.operators.LessThanOrEqualToOperator;
import moxl.xpath.parser.operators.MinusOperator;
import moxl.xpath.parser.operators.ModulusOperator;
import moxl.xpath.parser.operators.MultiplyOperator;
import moxl.xpath.parser.operators.NotEqualsOperator;
import moxl.xpath.parser.operators.OrOperator;
import moxl.xpath.parser.operators.PlusOperator;
import moxl.xpath.parser.types.AbstractXPathValue;


/**
 * Precedence is as follows:
 * <ul>
 * <li>union = 15
 * <li>unary = 10
 * <li>multiplicative = 9
 * <li>additive = 8
 * <li>relational = 6
 * <li>equality = 5
 * <li>and = 4
 * <li>or = 3
 * </ul>
 */
public abstract class AbstractOperator implements Comparable<AbstractOperator>
{
	public static final AbstractOperator PLUS = new PlusOperator(6);
	public static final AbstractOperator MINUS = new MinusOperator(6);
	public static final AbstractOperator MULTIPLY = new MultiplyOperator(9);
	public static final AbstractOperator DIVIDE = new DivideOperator(9);
	public static final AbstractOperator MODULUS_OPERATOR = new ModulusOperator(9);
	public static final AbstractOperator GREATER_THAN = new GreaterThanOperator(6);
	public static final AbstractOperator GREATER_THAN_EQUALS = new GreaterThanOrEqualToOperator(6);
	public static final AbstractOperator LESS_THAN_EQUALS = new LessThanOrEqualToOperator(6);
	public static final AbstractOperator LESS_THAN = new LessThanOperator(6);
	public static final AbstractOperator EQUALS = new EqualsOperator(6);
	public static final AbstractOperator NOT_EQUALS = new NotEqualsOperator(6);
	public static final AbstractOperator AND_OPERATOR = new AndOperator(4);
	public static final AbstractOperator OR_OPERATOR = new OrOperator(3);

	protected final static int LEFT_ASSOCIATIVE = 0;
	protected final static int RIGHT_ASSOCIATIVE = 1;

	private static Map<String, AbstractOperator> operatorsByImage;

	protected String image;
	private int precedence;

	protected AbstractOperator(String image, int precedence)
	{
		if (operatorsByImage == null)
		{
			operatorsByImage = new HashMap<String, AbstractOperator>();
		}
		if (operatorsByImage.containsKey(image))
		{
			throw new IllegalArgumentException("Operator already defined for '" + image + "'");
		}
		this.image = image;
		this.precedence = precedence;
		operatorsByImage.put(image, this);
	}

	public static AbstractOperator getOperator(String image)
	{
		AbstractOperator operator = (AbstractOperator) operatorsByImage.get(image);
		if (operator != null)
		{
			return operator;
		}
		else
		{
			throw new IllegalArgumentException("No operator defined for " + image);
		}
	}

	public abstract AbstractXPathValue evaluate(XPathContext ctx, AbstractXPathValue val1, AbstractXPathValue val2);

	public String getImage()
	{
		return image;
	}

	public int compareTo(AbstractOperator other)
	{
		if (getPrecedenceLevel() < other.getPrecedenceLevel())
		{
			return -1;
		}
		else if (getPrecedenceLevel() > other.getPrecedenceLevel())
		{
			return 1;
		}
		else if (this.equals(other))
		{
			if (getAssociativity() == LEFT_ASSOCIATIVE)
			{
				return 1;
			}
			else if (getAssociativity() == RIGHT_ASSOCIATIVE)
			{
				return -1;
			}
			else
			{
				throw new IllegalStateException("Associativity of " + getAssociativity() + " is not recognized.");
			}
		}
		else
		{
			return 1;
		}
	}

	public final int getPrecedenceLevel()
	{
		return precedence;
	}

	public int getAssociativity()
	{
		return LEFT_ASSOCIATIVE;
	}

}
